d3.bisector
是用來尋找某值對應一個陣列資料內的正確位置或最接近的位置,我在真實案例中使用到的地方是特定X軸對應的Y軸值。
範例:
const datas = [0, 1, 1, 2];
const index = d3.bisect(datas, 1);
console.log(index); // 3
透過d3.bisector
可以找出1插入的話,會位於第3個。其實就是2前面。
範例:
const datas = [0, 1, 1, 2];
const index = d3.bisectRight(datas, 1);
console.log(index); // 3
透過d3.bisectRight
可以找出1插入的話,並插入資料源的右側,會位於第3個。
範例:
const datas = [0, 1, 1, 2];
const index = d3.bisectLeft(datas, 1);
console.log(index); // 1
透過d3.bisectRight
可以找出1插入的話,並插入資料源的左側,會位於第1個。
範例:前半部可跳過
const datas = [
{
value: 10,
date: new Date('2020/08/25 01:00:00')
},
{
value: 123,
date: new Date('2020/08/25 02:00:00')
},
{
value: 56,
date: new Date('2020/08/25 03:00:00')
},
{
value: 98,
date: new Date('2020/08/25 05:00:00')
},
{
value: 30,
date: new Date('2020/08/25 06:00:00')
},
{
value: 156,
date: new Date('2020/08/25 07:00:00')
},
{
value: 20,
date: new Date('2020/08/25 08:00:00')
},
{
value: 12,
date: new Date('2020/08/25 09:00:00')
}
]
let width = 800;
let height = 600;
let padding = 50;
let innerWidth = width - padding * 2;
let innerHeight = height - padding * 2;
let svg = d3.select('svg')
.attr('width', width)
.attr('height', height)
.on('mousemove', mousemove)
let rootLayer = svg.append('g')
.attr('transform', `translate(${padding}, ${padding})`);
let axisLayer = rootLayer.append('g');
let lineLayer = rootLayer.append('g');
let xExtent = d3.extent(datas.map(data => data.date));
let yExtent = d3.extent(datas.map(data => data.value));
let xScale = d3.scaleTime().range([0, innerWidth]).domain(xExtent);
let yScale = d3.scaleLinear().range([innerHeight, 0]).domain(yExtent);
let xAxis = d3.axisBottom().scale(xScale).tickSize(-innerHeight);
let xAxisLayer = axisLayer.append('g').attr('transform', `translate(0, ${innerHeight})`).call(xAxis);
let yAxis = d3.axisLeft().scale(yScale).tickSize(-innerWidth);
let yAxisLayer = axisLayer.append('g').call(yAxis);
let circleLayer = rootLayer.append('g');
let line = d3.line()
.x(data => xScale(data.date))
.y(data => yScale(data.value))
let lines = lineLayer.selectAll('path')
.data([datas], data => data.date);
lines.enter()
.append('path')
.attr('d', line)
.attr('fill', 'none')
.attr('stroke', 'red')
.attr('stroke-width', 2)
// 這部分是重點
// 先產生一個bisect function. 並會目前得到的日期的左側位置。
const bisect = d3.bisector((b) => b.date).left;
function mousemove() {
const x = d3.pointer(event)[0] - padding;
const y = d3.pointer(event)[1] - padding;
const targetTime = xScale.invert(x);
// 推算出目前hover位置的Time
const i = bisect(datas, targetTime);
// 推出目前位置Time的對應第幾個
// 先移除Layer上的其他圓形
circleLayer.selectAll('*').remove();
// 直接append`Circle`並`cx``cy`帶入剛剛取得的序列對應的資料位置。
circleLayer
.append('circle')
.attr('r', 5)
.attr('cy', yScale(datas[i].value))
.attr('cx', xScale(datas[i].date))
.attr('fill', 'blue')
}
應用場景非常實際,當hover
時顯示特定附近的資料源,當然還有更多神奇的應用,像是一次取得特定位置x
多個資料源的資料,等等...